package com.vision.sales.sv.serv;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jasperreports.engine.JRDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ar.com.fdvs.dj.domain.DJCalculation;

import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.CustomSessionFactoryBean;
import com.vision.core.sv.db.mgr.DataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.util.ServletUtils;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.sales.cm.data.Month;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.data.TransactionStatus;
import com.vision.sales.cm.db.data.CollectionItem;
import com.vision.sales.cm.db.data.Customer;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.Salesman;
import com.vision.sales.sv.db.mgr.CollectionItemManager;
import com.vision.sales.sv.db.mgr.CustomerManager;
import com.vision.sales.sv.db.mgr.SalesInvoiceManager;
import com.vision.sales.sv.db.mgr.SalesmanManager;
import com.vision.sales.sv.report.AbstractDataSource;
import com.vision.sales.sv.report.Report;
import com.vision.sales.sv.report.ReportWriter;
import com.vision.sales.sv.report.SalesInvoiceRow;
import com.vision.sales.sv.serv.PrintSalesmanSalesServlet.SalesmanSalesParams;

/**
 * Servlet for printing Salesman Sales report.
 * 
 * This servlet is accessible via the /print/indiv_salesman_comm path in the URL (see web.xml)
 * 
 * GET parameters:
 * <ul>
 * 	<li>title - the title that will appear in the report
 * 	<li>year - the year to include (ex: 2012). Applies when type is either M or A.
 * 	<li>salesman - optional comma-separated list of salesman ids indicating which salesmans to include. If not specified, all salesmans will be included.</li>
 * 	<li>month - 1 as January, and so on
 * </ul>
 * 
 * Example:
 * <pre>/print/indiv_salesman_comm?year=2012&salesmans=5&months=12</pre>
 * 
 * @author Mark
 */
@Service("print_indiv_salesman_comm")
public class PrintIndividualSalesmanCommissionServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(PrintIndividualSalesmanCommissionServlet.class);
	
	@Autowired
	@Qualifier("salesmanManager")
	private DataManager<Salesman> salesmanManager;
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private DataManager<SalesInvoice> invoiceManager;
	
	@Autowired
	@Qualifier("customerManager")
	private DataManager<Customer> customerManager;
	
	@Autowired
	@Qualifier("collectionItemManager")
	private DataManager<CollectionItem> collectionItemManager;

	private List<Salesman> salesmen;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		try {
			ServletUtils.checkPermissions(SalesPermissions.ViewIndividualSalesmanSalesReport);
		} catch (RpcException e) {
			resp.sendError(HttpServletResponse.SC_FORBIDDEN, "You are not authorized to perform this operation.");
			return;
		} 
		
		// Parameters --
		log.debug("Parsing parameters from URL...");
		SalesmanSalesParams params = new SalesmanSalesParams(req);
		if (params.error != null) {
			resp.sendError(HttpServletResponse.SC_BAD_REQUEST, params.error);
			return;
		}
		
		try {
			// Build the Report
			log.debug("Building report...");
			Report report = new Report();
			report.setSize(params.size);
			report.setOrientation(params.orientation);
			
			setTitle(report, params);

			// Columns and Grouping
			// Date, Invoice No., Customer (code), Sales Amount
			report.addDateColumn("date", "Date", 20, "MM/dd/yyyy");
			report.addStringColumn("invoice", "Invoice#", 20, null);
			report.addStringColumn("customer", "Customer", 30, null);
			report.addDoubleColumn(SalesInvoiceRow.COLLECTION, "Collection", 30, DJCalculation.SUM);
			report.addDoubleColumn(SalesInvoiceRow.DISCOUNT_OVERPAY, "Discounts / OverPayment / Tax", 30, DJCalculation.SUM);
			report.addDoubleColumn(SalesInvoiceRow.NET_AMT, "Net Amount", 30, DJCalculation.SUM);
			report.addDoubleColumn(SalesInvoiceRow.COMM_AMT, "Commission Amount", 30, DJCalculation.SUM);
			
			// Data Source
			log.debug("Creating data source...");
			report.setDataSource(getDataSource(params));
			
			// Print and Write to Stream
			log.debug("Writing report to stream...");
			ReportWriter.write(report, resp);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The server was unable to process the request.");
		}
	}
	
	private void setTitle(Report report, SalesmanSalesParams params) {
		this.salesmen = getSalesmen(params);

		String salesmanName = "";
		if (this.salesmen.size() == 1)
		{
			Salesman salesman = this.salesmen.get(0);
			//salesmanHeader = salesman.getName() + " (" + salesman.getCode() + ") - " + getMonthName(params.months.get(0)) + " " + params.year;
			salesmanName = salesman.getName();
		}
		
		String monthYear = getMonthName(params.months.get(0)) + " " + params.year;
		String title = salesmanName  + " Commission ";
		report.setTitle(title);
		report.setFilename(title + monthYear);
		report.setSubtitle(monthYear);
	}

	private String getMonthName(int index) {
		return Month.values()[index - 1].toString();
	}

	private JRDataSource getDataSource(SalesmanSalesParams params) {
		// get collection items for the salesman
		List<Long> salesmanIds = CollectionUtils.listFrom(this.salesmen, "getId");
		List<Long> customerIds = this.getCustomers(salesmanIds);
		
		List<CollectionItem> collectionItems = getCollectionItems(params, customerIds);
		if (collectionItems.size() == 0)
		{
			return this.getDataSource(new ArrayList<SalesInvoiceRow>());
		}
		
		// get invoices
		List<Long> invoiceIds = CollectionUtils.listFrom(collectionItems, "getSalesInvoiceId");
		List<SalesInvoice> invoices = getInvoices(invoiceIds);
		
		Map<Long, SalesInvoiceRow> map = new HashMap<Long, SalesInvoiceRow>();
		Map<Long, Double> commRateMap = new HashMap<Long, Double>();
		
		for (SalesInvoice invoice : invoices) {
			map.put(invoice.getId(), new SalesInvoiceRow(invoice));
			commRateMap.put(invoice.getId(), invoice.getCommissionRate());
		}
		
		for (CollectionItem item : collectionItems) {
			Long invoiceId = item.getSalesInvoiceId();
			
			SalesInvoiceRow row = map.get(invoiceId);
			row.fillRow(SalesInvoiceRow.COLLECTION, item.getAmount());

			Double disc = item.getDiscounts();
			if (disc == null) {
				disc = 0.0;
			}

			Double over = item.getOverpayments();
			if (over == null){
				over = 0.0;
			}
			
			double tax = item.getTax(); // Tax is non-nullable
			
			row.fillRow(SalesInvoiceRow.DISCOUNT_OVERPAY, disc + over + tax);
			row.fillRow(SalesInvoiceRow.NET_AMT, item.getNetAmount());
			
			Double rate = commRateMap.get(invoiceId);
			row.fillRow(SalesInvoiceRow.COMM_AMT, item.getNetAmount() * (rate / 100));
		}
		

		List<SalesInvoiceRow> rows = new ArrayList<SalesInvoiceRow>(map.values()); 
		Collections.sort(rows);
		
		return this.getDataSource(rows);
	}

	private JRDataSource getDataSource(List<SalesInvoiceRow> rows) {
		JRDataSource ds = new AbstractDataSource<SalesInvoiceRow>(rows) {
			@Override
			public Object getValue(SalesInvoiceRow object, String fieldName) {
				return object.get(fieldName);
			}
		};
		return ds;
	}

	@SuppressWarnings("unchecked")
	protected List<Long> getCustomers(List<Long> salesmanIds) {
		// XXX @Autowired doesn't seem to work, thus this patch
		customerManager = new CustomerManager();
		customerManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(Customer.class);

		if (salesmanIds != null && !salesmanIds.isEmpty())
			crit.in("salesman.id", salesmanIds);
		
		log.debug("Retrieving customers...");
		List<Customer> customers = customerManager.getTemplate().findByCriteria(crit.getDetached());

		List<Long> customerIds = CollectionUtils.listFrom(customers, "getId");
		
		return customerIds;
	}


	@SuppressWarnings("unchecked")
	protected List<SalesInvoice> getInvoices(List<Long> invoiceIds) {
		// XXX @Autowired doesn't seem to work, thus this patch
		invoiceManager = new SalesInvoiceManager();
		invoiceManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);
		crit.eq("status", TransactionStatus.POSTED);
		
		if (invoiceIds != null && !invoiceIds.isEmpty())
			crit.in("id", invoiceIds);
		
		crit.asc("date");
		
		log.debug("Retrieving sales invoices...");
		List<SalesInvoice> invoices = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return invoices;
	}

	
	@SuppressWarnings("unchecked")
	private List<CollectionItem> getCollectionItems(SalesmanSalesParams params, List<Long> customerIds) {
		// XXX @Autowired doesn't seem to work, thus this patch
		collectionItemManager = new CollectionItemManager();
		collectionItemManager.setSessionFactory(CustomSessionFactoryBean.getFactory());

		ISimpleCriteria crit = SimpleCriteria.forClass(CollectionItem.class);
		// TODO Get collections first then get CollectionItem
//		crit.eq("collection.status", TransactionStatus.POSTED);
		
		crit.add(DateExtract.year("date", params.year));
		
		crit.add(DateExtract.in("MONTH", "date", params.months));
		
		if (params.companyIds != null && !params.companyIds.isEmpty())
			crit.in("company.id", params.companyIds);
		
		if (customerIds != null && !customerIds.isEmpty())
			crit.in("customer.id", customerIds);
		
		crit.asc("date");
		
		log.debug("Retrieving collection items...");
		List<CollectionItem> items = collectionItemManager.getTemplate().findByCriteria(crit.getDetached());
		return items;
	}
	
	protected List<Salesman> getSalesmen(SalesmanSalesParams params) {
		// XXX @Autowired doesn't seem to work, thus this patch
		salesmanManager = new SalesmanManager();
		salesmanManager.setSessionFactory(CustomSessionFactoryBean.getFactory());
		
		log.debug("Retrieving salesmans...");
		List<Salesman> salesmans = null;
		if (params.salesmanIds == null || params.salesmanIds.isEmpty())
			salesmans = salesmanManager.getAll();
		else
			salesmans = salesmanManager.getByIds("id", params.salesmanIds);
		
		return salesmans;
	}
}
